package main

import (
	"bytes"
	"fmt"
	"regexp"
	"text/template"

	"github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
	"github.com/gogo/protobuf/vanity/command"
)

const t = `
{{range .Messages}}
{{if .ID}}
{{if .Comment}}{{.Comment}}{{end}}
var {{.CamelCaseName}} = {
    {{.CamelCaseName}}:{
{{.NewBody}}
    }
};
{{else}}
{{if .Comment}}{{.Comment}}{{end}}
var {{.CamelCaseName}} = {
{{.NewBody}}
};
{{end}}
{{end}}
`

var msgRegexp = regexp.MustCompile(`\s*@msg(?:\s*=\s*(\d+))?\s*`)

type messageField struct {
	md *Descriptor
	dp *descriptor.FieldDescriptorProto

	Name          string
	TypeName      string
	JsonName      string
	CamelCaseName string
	Comment       string
}

type message struct {
	g *Generator

	ID            string
	Name          string
	CamelCaseName string
	Type          string
	Comment       string
	Fields        []*messageField
}

func (m *message) NewBody() string {
	src := ""
	lenField := len(m.Fields)
	indent := "        "
	if m.ID == "" {
		indent = "    "
	}
	for i, f := range m.Fields {
		switch *f.dp.Type {
		case descriptor.FieldDescriptorProto_TYPE_INT32,
			descriptor.FieldDescriptorProto_TYPE_UINT32,
			descriptor.FieldDescriptorProto_TYPE_INT64,
			descriptor.FieldDescriptorProto_TYPE_UINT64,
			descriptor.FieldDescriptorProto_TYPE_SINT32,
			descriptor.FieldDescriptorProto_TYPE_SINT64,
			descriptor.FieldDescriptorProto_TYPE_ENUM:
			if !IsRepeated(f.dp) {
				if i < lenField-1 {
					src += fmt.Sprintf("%s%s:0, %s\n", indent, f.CamelCaseName, f.Comment)
				} else {
					src += fmt.Sprintf("%s%s:0 %s", indent, f.CamelCaseName, f.Comment)
				}
			} else {
				if i < lenField-1 {
					src += fmt.Sprintf("%s%s:[], %s\n", indent, f.CamelCaseName, f.Comment)
				} else {
					src += fmt.Sprintf("%s%s:[] %s", indent, f.CamelCaseName, f.Comment)
				}
			}

		case descriptor.FieldDescriptorProto_TYPE_STRING:
			if !IsRepeated(f.dp) {
				if i < lenField-1 {
					src += fmt.Sprintf(`%s%s:"", %s
`, indent, f.CamelCaseName, f.Comment)
				} else {
					src += fmt.Sprintf(`%s%s:"" %s`, indent, f.CamelCaseName, f.Comment)
				}
			} else {
				if i < lenField-1 {
					src += fmt.Sprintf("%s%s:[], %s\n", indent, f.CamelCaseName, f.Comment)
				} else {
					src += fmt.Sprintf("%s%s:[] %s", indent, f.CamelCaseName, f.Comment)
				}
			}

		case descriptor.FieldDescriptorProto_TYPE_DOUBLE,
			descriptor.FieldDescriptorProto_TYPE_FLOAT,
			descriptor.FieldDescriptorProto_TYPE_FIXED32,
			descriptor.FieldDescriptorProto_TYPE_FIXED64,
			descriptor.FieldDescriptorProto_TYPE_SFIXED32,
			descriptor.FieldDescriptorProto_TYPE_SFIXED64:
			if !IsRepeated(f.dp) {
				if i < lenField-1 {
					src += fmt.Sprintf("%s%s:0, %s\n", indent, f.CamelCaseName, f.Comment)
				} else {
					src += fmt.Sprintf("%s%s:0 %s", indent, f.CamelCaseName, f.Comment)
				}
			} else {
				if i < lenField-1 {
					src += fmt.Sprintf("%s%s:[], %s\n", indent, f.CamelCaseName, f.Comment)
				} else {
					src += fmt.Sprintf("%s%s:[] %s", indent, f.CamelCaseName, f.Comment)
				}
			}

		case descriptor.FieldDescriptorProto_TYPE_MESSAGE:
			if !IsRepeated(f.dp) {
				if i < lenField-1 {
					src += fmt.Sprintf("%s%s:{}, //%s %s\n", indent, f.CamelCaseName, f.TypeName, f.Comment)
				} else {
					src += fmt.Sprintf("%s%s:{} //%s %s", indent, f.CamelCaseName, f.TypeName, f.Comment)
				}
			} else {
				if i < lenField-1 {
					src += fmt.Sprintf("%s%s:[], //%s %s\n", indent, f.CamelCaseName, f.TypeName, f.Comment)
				} else {
					src += fmt.Sprintf("%s%s:[] //%s %s", indent, f.CamelCaseName, f.TypeName, f.Comment)
				}
			}

		default:
			if !IsRepeated(f.dp) {
				if i < lenField-1 {
					src += fmt.Sprintf("%s%s:{}, %s\n", indent, f.CamelCaseName, f.Comment)
				} else {
					src += fmt.Sprintf("%s%s:{} %s", indent, f.CamelCaseName, f.Comment)
				}
			} else {
				if i < lenField-1 {
					src += fmt.Sprintf("%s%s:[], %s\n", indent, f.CamelCaseName, f.Comment)
				} else {
					src += fmt.Sprintf("%s%s:[] %s", indent, f.CamelCaseName, f.Comment)
				}
			}
		}
	}
	return src
}

func (m *message) ResetBody() string {
	src := ""
	for _, f := range m.Fields {
		typeName, _ := m.g.GoType(f.md, f.dp)
		switch *f.dp.Type {
		case descriptor.FieldDescriptorProto_TYPE_MESSAGE:
			if !IsRepeated(f.dp) {
				src += fmt.Sprintf("m.%s.Reset()\n", f.CamelCaseName)
			} else {
				src += fmt.Sprintf(`
				for _, i := range m.%s {
					Put%s(i)
				}
				m.%s = %s{}
			   	`, f.CamelCaseName, GoTypeToName(typeName), f.CamelCaseName, typeName)
			}

		case descriptor.FieldDescriptorProto_TYPE_INT32,
			descriptor.FieldDescriptorProto_TYPE_UINT32,
			descriptor.FieldDescriptorProto_TYPE_INT64,
			descriptor.FieldDescriptorProto_TYPE_UINT64,
			descriptor.FieldDescriptorProto_TYPE_SINT32,
			descriptor.FieldDescriptorProto_TYPE_SINT64,
			descriptor.FieldDescriptorProto_TYPE_ENUM:
			if !IsRepeated(f.dp) {
				src += fmt.Sprintf("m.%s = 0\n", f.CamelCaseName)
			} else {
				src += fmt.Sprintf("m.%s = []%s{}\n", f.CamelCaseName, GoTypeToName(typeName))
			}

		case descriptor.FieldDescriptorProto_TYPE_BOOL:
			if !IsRepeated(f.dp) {
				src += fmt.Sprintf("m.%s = false\n", f.CamelCaseName)
			} else {
				src += fmt.Sprintf("m.%s = []%s{}\n", f.CamelCaseName, GoTypeToName(typeName))
			}

		case descriptor.FieldDescriptorProto_TYPE_STRING:
			if !IsRepeated(f.dp) {
				src += fmt.Sprintf("m.%s = \"\"\n", f.CamelCaseName)
			} else {
				src += fmt.Sprintf("m.%s = []%s{}\n", f.CamelCaseName, GoTypeToName(typeName))
			}

		case descriptor.FieldDescriptorProto_TYPE_DOUBLE,
			descriptor.FieldDescriptorProto_TYPE_FLOAT,
			descriptor.FieldDescriptorProto_TYPE_FIXED32,
			descriptor.FieldDescriptorProto_TYPE_FIXED64,
			descriptor.FieldDescriptorProto_TYPE_SFIXED32,
			descriptor.FieldDescriptorProto_TYPE_SFIXED64:
			if !IsRepeated(f.dp) {
				src += fmt.Sprintf("m.%s = 0.0\n", f.CamelCaseName)
			} else {
				src += fmt.Sprintf("m.%s = []%s{}\n", f.CamelCaseName, GoTypeToName(typeName))
			}

		case descriptor.FieldDescriptorProto_TYPE_BYTES:
			if !IsRepeated(f.dp) {
				src += fmt.Sprintf("m.%s = []byte{}\n", f.CamelCaseName)
			} else {
				src += fmt.Sprintf("m.%s = [][]%s{}\n", f.CamelCaseName, GoTypeToName(typeName))
			}
		}
	}
	return src
}

type enumValue struct {
	ed *EnumDescriptor
	dp *descriptor.EnumValueDescriptorProto

	Name     string
	TypeName string
	Value    int32
	Comment  string
}

func (ev *enumValue) Prefix() string {
	return ev.ed.Prefix()
}

type enum struct {
	g  *Generator
	ed *EnumDescriptor

	Name          string
	CamelCaseName string
	Comment       string
	Values        []*enumValue
}

func (e *enum) Prefix() string {
	return e.ed.Prefix()
}

type protoFile struct {
	PackageName string
	Messages    []*message
	Enums       []*enum
}

func parse(g *Generator, fd *FileDescriptor) *protoFile {
	pf := &protoFile{}

	pf.PackageName = fd.PackageName()

	for _, md := range fd.Messages() {
		m := &message{
			g:       g,
			Name:    CamelCaseSlice(md.TypeName()),
			Comment: g.GetComments(md.Path()),
		}
		m.CamelCaseName = CamelCase(m.Name)

		if matches := msgRegexp.FindStringSubmatch(m.Comment); len(matches) > 0 {
			m.ID = m.CamelCaseName
			//m.Comment = strings.Trim(m.Comment, matches[0])
		}

		for j, fdp := range md.GetField() {
			f := &messageField{
				md:       md,
				dp:       fdp,
				Name:     g.GetFieldName(md, fdp),
				JsonName: fdp.GetJsonName(),
				Comment:  g.GetComments(fmt.Sprintf("%s,%d,%d", md.Path(), 2, j)),
			}
			f.CamelCaseName = CamelCase(f.Name)
			f.TypeName, _ = g.GoType(md, fdp)

			m.Fields = append(m.Fields, f)
		}

		pf.Messages = append(pf.Messages, m)
	}

	for _, ed := range fd.Enums() {
		name := ed.GetName()
		if name == "MSG" {
			// 跳过叫MSG的枚举
			continue
		}

		if ed.Parent() != nil {
			name = ed.Prefix() + name
		}

		e := &enum{
			g:             g,
			ed:            ed,
			Name:          name,
			CamelCaseName: CamelCase(name),
			Comment:       g.GetComments(ed.Path()),
		}

		for j, edp := range ed.GetValue() {
			v := &enumValue{
				ed:       ed,
				dp:       edp,
				Name:     *edp.Name,
				TypeName: e.CamelCaseName,
				Value:    *edp.Number,
				Comment:  g.GetComments(fmt.Sprintf("%s,2,%d", ed.Path(), j)),
			}

			e.Values = append(e.Values, v)
		}

		pf.Enums = append(pf.Enums, e)
	}

	return pf
}

type pluginTS struct {
	*Generator
}

func newPlugin() *pluginTS {
	p := &pluginTS{}
	return p
}

func (pluginTS) Name() string {
	return "myjson-ts"
}

func (p *pluginTS) Init(g *Generator) {
	p.Generator = g
}

func (p *pluginTS) Generate(fd *FileDescriptor) {
	var code bytes.Buffer
	tpl.Execute(&code, parse(p.Generator, fd))
	s := code.String()
	p.P(s)
}

func (p *pluginTS) GenerateImports(fd *FileDescriptor) {
}

var (
	tpl *template.Template
)

func init() {
	var err error
	tpl, err = template.New("myjson-ts").Parse(t)
	if err != nil {
		panic(err)
	}
}

func main() {
	req := command.Read()
	p := newPlugin()
	res := Generate(req, p, "Msg.ts")
	command.Write(res)
}
